""" CheckBoxDelegateQt.py: Delegate for editing bool values via a checkbox with no label centered in its cell.
"""


from PyQt5.QtCore import Qt, QEvent, QPoint, QRect
from PyQt5.QtWidgets import QComboBox,QLineEdit, QStyledItemDelegate, QStyleOptionButton, QStyle, QApplication
from PyQt5 import QtWidgets
import traceback
def is_float(a_string):
    try:
        float(a_string)
        return True
    except ValueError:
        return False
__author__ = "Marcel Goldschen-Ohm <marcel.goldschen@gmail.com>"
class doubleDelegate(QtWidgets.QItemDelegate):
    def createEditor(self,parent, option,index):
        editor =QtWidgets.QLineEdit(parent)
        return editor;
    def setEditorData(self,editor, index):
        value = index.model().data(index)
        editor.setText(str(value))
    def setModelData(self,editor,model,index):
        value = editor.text()
        model.setData(index, value)
    def updateEditorGeometry(self,editor,option,index):
        editor.setGeometry(option.rect)
class ComboBoxDelegateQt(QStyledItemDelegate):
    """ Delegate for editing a list of choices via a combobox.
    The choices attribute is a list of either values or (key, value) tuples.
    In the first case, the str rep of the values are directly displayed in the combobox.
    In the latter case, the str rep of only the keys are displayed in the combobox, and the values can be any object.
        Although only the keys are displayed in the view, the model data is set to the actual values, not the keys.
    Upon selection, view will display the str rep of either the value itself or its key if it exists.

    For example:
        To select from some integers, set choices = [1, 3, 10, 100].
            Combobox entries will be '1', '3', '10' and '100'.
            Upon selection model data will be set to the selected integer value and view will show the str rep of this value.
        To select from two of your custom objects, set choices = [('A', MyObject()), ('B', MyObject())]
            Combobox entries will be 'A' and 'B'.
            Upon selection model data will be set to the selected MyObject instance and view will show its key (either 'A' or 'B')..
    """
    def __init__(self, choices=None, parent=None):
        super().__init__(parent)
        self.choices = choices if (choices is not None and type(choices) is list) else []

    def createEditor(self, parent, option, index):
        """ Return QComboBox with list of choices (either values or their associated keys if they exist).
        """
        try:
            editor = QComboBox(parent)
            value = index.model().data(index, Qt.DisplayRole)
            for i, choice in enumerate(self.choices):
                if (type(choice) is tuple) and (len(choice) == 2):
                    # choice is a (key, value) tuple.
                    key, val = choice
                    editor.addItem(str(key))  # key MUST be representable as a str.
                    if val == value:
                        editor.setCurrentIndex(i)
                else:
                    # choice is a value.
                    editor.addItem(str(choice))  # choice MUST be representable as a str.
                    if choice == value:
                        editor.setCurrentIndex(i)
            return editor
        except:
            return None

    def setModelData(self, editor, model, index):
        """ Set model data to current choice (if choice is a key, set data to its associated value).
        """
        try:
            choice = self.choices[editor.currentIndex()]
            if (type(choice) is tuple) and (len(choice) == 2):
                # choice is a (key, value) tuple.
                key, val = choice
                value = copy.deepcopy(val)  # Deepcopy of val in case it is a complex object.
            else:
                # choice is a value.
                value = choice
            model.setData(index, value, Qt.EditRole)
            index.model().dataChanged.emit(index, index)  # Tell model to update cell display.
        except:
            traceback.print_exc()
            pass

    def displayText(self, value, locale):
        """ Show str rep of current choice (or choice key if choice is a (key, value) tuple).
        """
        try:
            for choice in self.choices:
                if (type(choice) is tuple) and (len(choice) == 2):
                    # choice is a (key, value) tuple.
                    # Display the key, not the value.
                    key, val = choice
                    if val == value:
                        return str(key)
                else:
                    # choice is a value.
                    # Display it's str rep.
                    if choice == value:
                        return str(choice)
            # If value is not in our list of choices, show str rep of value.
            return str(value)
        except:
            traceback.print_exc()
            return ""

class CheckBoxDelegateQt(QStyledItemDelegate):
    """ Delegate for editing bool values via a checkbox with no label centered in its cell.
    Does not actually create a QCheckBox, but instead overrides the paint() method to draw the checkbox directly.
    Mouse events are handled by the editorEvent() method which updates the model's bool value.
    """
    def __init__(self, parent=None):
        super().__init__(parent)

    def createEditor(self, parent, option, index):
        """ Important, otherwise an editor is created if the user clicks in this cell.
        """
        return None

    def paint(self, painter, option, index):
        """ Paint a checkbox without the label.
        """
        checked = bool(index.model().data(index, Qt.DisplayRole))
        opts = QStyleOptionButton()
        opts.state |= QStyle.State_Active
        if index.flags() & Qt.ItemIsEditable:
            opts.state |= QStyle.State_Enabled
        else:
            opts.state |= QStyle.State_ReadOnly
        if checked:
            opts.state |= QStyle.State_On
        else:
            opts.state |= QStyle.State_Off
        opts.rect = self.getCheckBoxRect(option)
        QApplication.style().drawControl(QStyle.CE_CheckBox, opts, painter)

    def editorEvent(self, event, model, option, index):
        """ Change the data in the model and the state of the checkbox if the
        user presses the left mouse button and this cell is editable. Otherwise do nothing.
        """
        if not (index.flags() & Qt.ItemIsEditable):
            return False
        if event.button() == Qt.LeftButton:
            if event.type() == QEvent.MouseButtonRelease:
                if self.getCheckBoxRect(option).contains(event.pos()):
                    self.setModelData(None, model, index)
                    return True
            elif event.type() == QEvent.MouseButtonDblClick:
                if self.getCheckBoxRect(option).contains(event.pos()):
                    return True
        return False

    def setModelData(self, editor, model, index):
        """ Toggle the boolean state in the model.
        """
        checked = not bool(index.model().data(index, Qt.DisplayRole))
        model.setData(index, checked, Qt.EditRole)

    def getCheckBoxRect(self, option):
        """ Get rect for checkbox centered in option.rect.
        """
        # Get size of a standard checkbox.
        opts = QStyleOptionButton()
        checkBoxRect = QApplication.style().subElementRect(QStyle.SE_CheckBoxIndicator, opts, None)
        # Center checkbox in option.rect.
        x = option.rect.x()
        y = option.rect.y()
        w = option.rect.width()
        h = option.rect.height()
        checkBoxTopLeftCorner = QPoint(int(x + w / 2 - checkBoxRect.width() / 2),int(y + h / 2 - checkBoxRect.height() / 2))
        return QRect(checkBoxTopLeftCorner, checkBoxRect.size())

class DoubleDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        """ Return QComboBox with list of choices (either values or their associated keys if they exist).
        """
        return None

    def paint(self, painter, option, index):
        value = index.model().data(index)
        if is_float(value):
            option.text = "%0.7f" % float(value)
        else:
            option.text = value
        widget = option.widget
        if widget:
            style = widget.style()
        else:
            style = QApplication.style()
        style.drawControl(QStyle.CE_ItemViewItem, option, painter, widget)
class NotEditDelegate(QtWidgets.QStyledItemDelegate):
    def createEditor(self, parent, option, index):
        """ Return QComboBox with list of choices (either values or their associated keys if they exist).
        """
        return None
class FloatEditDelegateQt(QStyledItemDelegate):
    """ Delegate for editing float values with arbitrary precision that may also be in scientific notation.
    Based on a QLineEdit rather than Qt's default QDoubleSpinBox that limits values to two decimal places
    and cannot handle scientific notation.
    """
    def __init__(self, parent=None):
        super().__init__(parent)

    def createEditor(self, parent, option, index):
        """ Return a QLineEdit for arbitrary representation of a float value.
        """
        editor = QLineEdit(parent)
        value = index.model().data(index, Qt.DisplayRole)
        editor.setText(str(value))
        return editor

    def setModelData(self, editor, model, index):
        """ Cast the QLineEdit text to a float value, and update the model with this value.
        """
        try:
            value = float(editor.text())
            model.setData(index, value, Qt.EditRole)
        except:
            traceback.print_exc()
            pass  # If we can't cast the user's input to a float, don't do anything.
class ComboBoxCompleteDelegate(QStyledItemDelegate):
    """ Delegate for editing a list of choices via a combobox.
    The choices attribute is a list of either values or (key, value) tuples.
    In the first case, the str rep of the values are directly displayed in the combobox.
    In the latter case, the str rep of only the keys are displayed in the combobox, and the values can be any object.
        Although only the keys are displayed in the view, the model data is set to the actual values, not the keys.
    Upon selection, view will display the str rep of either the value itself or its key if it exists.

    For example:
        To select from some integers, set choices = [1, 3, 10, 100].
            Combobox entries will be '1', '3', '10' and '100'.
            Upon selection model data will be set to the selected integer value and view will show the str rep of this value.
        To select from two of your custom objects, set choices = [('A', MyObject()), ('B', MyObject())]
            Combobox entries will be 'A' and 'B'.
            Upon selection model data will be set to the selected MyObject instance and view will show its key (either 'A' or 'B')..
    """
    def __init__(self, choices=None,completer=None, parent=None):
        super().__init__(parent)
        self.completer=completer
        self.choices = choices if (choices is not None and type(choices) is list) else []

    def createEditor(self, parent, option, index):
        """ Return QComboBox with list of choices (either values or their associated keys if they exist).
        """
        try:
            editor = QComboBox(parent)
            editor.setEditable(True)
            editor.setCompleter(self.completer)
            value = index.model().data(index, Qt.DisplayRole)
            for i, choice in enumerate(self.choices):
                if (type(choice) is tuple) and (len(choice) == 2):
                    # choice is a (key, value) tuple.
                    key, val = choice
                    editor.addItem(str(key))  # key MUST be representable as a str.
                    if val == value:
                        editor.setCurrentIndex(i)
                else:
                    # choice is a value.
                    editor.addItem(str(choice))  # choice MUST be representable as a str.
                    if choice == value:
                        editor.setCurrentIndex(i)
            return editor
        except:
            return None

    def setModelData(self, editor, model, index):
        """ Set model data to current choice (if choice is a key, set data to its associated value).
        """
        try:
            choice = self.choices[editor.currentIndex()]
            if (type(choice) is tuple) and (len(choice) == 2):
                # choice is a (key, value) tuple.
                key, val = choice
                value = copy.deepcopy(val)  # Deepcopy of val in case it is a complex object.
            else:
                # choice is a value.
                value = choice
            model.setData(index, value, Qt.EditRole)
            index.model().dataChanged.emit(index, index)  # Tell model to update cell display.
        except:
            traceback.print_exc()
            pass

    def displayText(self, value, locale):
        """ Show str rep of current choice (or choice key if choice is a (key, value) tuple).
        """
        try:
            for choice in self.choices:
                if (type(choice) is tuple) and (len(choice) == 2):
                    # choice is a (key, value) tuple.
                    # Display the key, not the value.
                    key, val = choice
                    if val == value:
                        return str(key)
                else:
                    # choice is a value.
                    # Display it's str rep.
                    if choice == value:
                        return str(choice)
            # If value is not in our list of choices, show str rep of value.
            return str(value)
        except:
            traceback.print_exc()
            return ""
